#ifndef NPC_H
#define NPC_H

#include <vector>
#include <stack>
#include <iostream>
#include "player.h"
#include "tileNode.h"
#include "map.h"
#include "item.h"

using namespace std;

class npc{
 public:
	npc(int x, int y, int graphicId, int speed, player* dude, map * mapOne);
	~npc();

	int x;
	int y;
	int width;
	int height;
	int graphicId;
	int speed;
	int goal_x;
	int goal_y;
	
	map* mapOne;
	player* dude;
	
	int health;
	bool moving;
	bool underAttack;
	bool violent;
	bool live;
	int currentItem;
	
	void moveLeft();
	void moveRight();
	void moveUp();
	void moveDown();
	vector<item *> items;
	vector <tileNode *> closed;
	vector <tileNode *> opened;
	stack <tileNode *> pathStack;
	vector <int> wayPointY;
	vector <int> wayPointX;
	void attackPlayer();
	void hurt(int numDamage);
	void changeToNextPoint();
	void move();
	void updateNpc();
	bool moveToGoalCheck();
	void moveX();
	void moveY();
	void isUnderAttack();
	void dead();
	void setGoal(int newGoalX, int newGoalY);
	
	void addItems(item* itemz);
	
	void addWayPoints(int pointX, int pointY);
	void aStar();
	vector<tileNode*> surrounding(tileNode* node);
	tileNode* putIntoQueue(tileNode* current);
	void clickAdjustment();
	void findG(int num, tileNode* node);
	void findH(tileNode* node);
	void findF(tileNode* node);
	bool checkQueue(int nx, int ny);
	bool checkVector(int nx, int ny);
	void putIntoTemptVector(int nx, int ny, vector<tileNode*> temp, int num);
	void getAdjacent(tileNode* currentNode);
	bool solid(int nx, int ny);
	void moveParent(tileNode* node, tileNode* current, int num);
	tileNode* findNode(int nx, int ny);
	void backtrack();
	void clearStack();
	
 private:
};

//Constructor: sets the x and y coordinate, sets the id of the kind of
//character it is, the speed that the character should move, 
//a pointer to the player object, and a pointer to a map object
npc::npc(int x, int y, int graphicId, int speed, player* dude, map* mapOne){
	this->x=x;
	this->y=y;
	this->graphicId=graphicId;
	this->speed=speed;
	this->dude=dude;
	this->mapOne=mapOne;
	height=32;
	width=32;
	health=100;
	moving = false;
	live=true;
}

//Dectructor
npc::~npc(){
}

//changes the underAttack boolean value to true
void npc::isUnderAttack(){
	underAttack=true;
}

//check to see in the health of the non playable character is less than
//or equal to 0. If not then it is dead.
void npc::dead(){
	if(health<=0)
	live=false;
}

//Attacks the player by using it's item
void npc::attackPlayer(){
	dude->hurt(items[0]->getDamage());
}

//takes away health from the non playable character using the passed in 
//value
void npc::hurt(int numDamage){
	health-=numDamage;
}

//add items to the item array for the non playable character to carry
void npc::addItems(item* itemz){
	items.push_back(itemz);
}

//add a waypoint for the non playable character to go to when patroling
void npc::addWayPoints(int pointX, int pointY){
	this->wayPointX.push_back(pointX);
	this->wayPointY.push_back(pointY);
}

//moves the non playable character left 
void npc::moveLeft(){
	x-=speed;
}

//moves the non playable character right
void npc::moveRight(){
	x+=speed;
}

//moves the non playable character up
void npc::moveUp(){
	y-=speed;
}

//moves the non playable character down
void npc::moveDown(){
	y+=speed;
}

//check if non playable character has reached it's goal
bool npc::moveToGoalCheck(){
	if(goal_x==x&&goal_y==y){
		clearStack();
		moving=false;
		return true;
	}
	else return false;
}

//change the goal to the next waypoint
void npc::changeToNextPoint(){
	int size=wayPointX.size();
	for(int i = 0; i < size; i++){
		if(i==size-1){
			goal_x=wayPointX[0];
			goal_y=wayPointY[0];
			break;
		}
		else if(wayPointX[i]==x&&wayPointY[i]==y){
			goal_x=wayPointX[i+1];
			goal_y=wayPointY[i+1];
			break;
		}
	}
	move();
}

//set the goal coordinates of the non playable character to the 
//two inputs
void npc::setGoal(int newGoalX, int newGoalY){
	clearStack();
	goal_x=(newGoalX/16)*16;
	goal_y=(newGoalY/16)*16;
	clickAdjustment();
	move();
}

//check to see if the goal is not equal to the x and y coordinates
//if it isn't it will call the A* algorithm and turn the boolean moving to
//true  
void npc::move(){
	if((goal_x!=x||goal_y!=y)){
		clearStack();
		aStar();
		moving=true;
	}
}

//moves the player to it's goals through the pathStack coordinates
void npc::updateNpc(){
	if(!pathStack.empty()&&moving){
	if(pathStack.top()->y!=y||pathStack.top()->x!=x){
		moveX();
		moveY();
	}
	else pathStack.pop();
	}
}

//move in the x direction
void npc::moveX(){
	if(x<pathStack.top()->x)
		moveRight();
	else if(x>pathStack.top()->x)
		moveLeft();
}

//move in the y direction
void npc::moveY(){
	if(y<pathStack.top()->y)
		moveDown();
	else if(y>pathStack.top()->y)
		moveUp();
}

//The a star algorithm
void npc::aStar(){
	tileNode* newNode= new tileNode((x/16)*16,(y/16)*16);
	tileNode* currentNode = newNode;
	currentNode->parent = NULL;
	currentNode->G=0;
	findH(currentNode);
	findF(currentNode);
	opened.push_back(currentNode);
	
	while(!opened.empty()){
		if(currentNode->x==goal_x&&currentNode->y==goal_y)
			break;
		currentNode = putIntoQueue(currentNode);
		getAdjacent(currentNode);
	}
	backtrack();
	opened.clear();
	closed.clear();
}

void npc::getAdjacent(tileNode* currentNode){
	tileNode* node;
	int nx=currentNode->x;
	int ny=currentNode->y;
	
	//Up
		ny-=16;
	if(solid(nx,ny)==true){
		
		if(checkQueue(nx,ny)==true&&checkVector(nx,ny)==true){
			//cout<<" ("<<nx<<","<<ny<<") ";
		node = new tileNode(nx,ny);
		node->parent=currentNode;
		findG(16,node);
		findH(node);
		findF(node);
		opened.push_back(node);
		}
		else if(checkVector(nx,ny)==false){
			node = findNode(nx,ny);
			moveParent(node, currentNode, 16);
		}
	}
	//Diagonal Right Up
		nx+=16;
	if(solid(nx,ny)==true){
		
		if(checkQueue(nx,ny)==true&&checkVector(nx,ny)==true){
			//cout<<" ("<<nx<<","<<ny<<") ";
		node = new tileNode(nx,ny);
		node->parent=currentNode;
		findG(22,node);
		findH(node);
		findF(node);
		opened.push_back(node);
		}
		else if(checkVector(nx,ny)==false){
			node = findNode(nx, ny);
			moveParent(node, currentNode, 22);
		}
	}
	//Right
		ny+=16;
	if(solid(nx,ny)==true){
		
		if(checkQueue(nx,ny)==true&&checkVector(nx,ny)==true){
			//cout<<" ("<<nx<<","<<ny<<") ";
		node = new tileNode(nx,ny);
		node->parent=currentNode;
		findG(16,node);
		findH(node);
		findF(node);
		opened.push_back(node);
		}
		else if(checkVector(nx,ny)==false){
			node = findNode(nx, ny);
			moveParent(node, currentNode, 16);
		}
	}
	//Diagonal Right Down
		ny+=16;
	if(solid(nx,ny)==true){
		
		if(checkQueue(nx,ny)==true&&checkVector(nx,ny)==true){
			//cout<<" ("<<nx<<","<<ny<<") ";
		node = new tileNode(nx,ny);
		node->parent=currentNode;
		findG(22,node);
		findH(node);
		findF(node);
		opened.push_back(node);
		}
		else if(checkVector(nx,ny)==false){
			node = findNode(nx, ny);
			moveParent(node, currentNode, 22);
		}
	}
	//Down
		nx-=16;
	if(solid(nx,ny)==true){
		
		if(checkQueue(nx,ny)==true&&checkVector(nx,ny)==true){
			//cout<<" ("<<nx<<","<<ny<<") ";
		node = new tileNode(nx,ny);
		node->parent=currentNode;
		findG(16,node);
		findH(node);
		findF(node);
		opened.push_back(node);
		}
		else if(checkVector(nx,ny)==false){
			node = findNode(nx, ny);
			moveParent(node, currentNode, 16);
		}
	}
	//Diagonal Left Down
		nx-=16;
	if(solid(nx,ny)==true){
	
		if(checkQueue(nx,ny)==true&&checkVector(nx,ny)==true){
			//cout<<" ("<<nx<<","<<ny<<") ";
		node = new tileNode(nx,ny);
		node->parent=currentNode;
		findG(22,node);
		findH(node);
		findF(node);
		opened.push_back(node);
		}
		else if(checkVector(nx,ny)==false){
			node = findNode(nx, ny);
			moveParent(node, currentNode, 22);
		}
	}
	//Left
		ny-=16;
	if(solid(nx,ny)==true){
		;
		if(checkQueue(nx,ny)==true&&checkVector(nx,ny)==true){
			//cout<<" ("<<nx<<","<<ny<<") ";
		node = new tileNode(nx,ny);
		node->parent=currentNode;
		findG(16,node);
		findH(node);
		findF(node);
		opened.push_back(node);
		}
		else if(checkVector(nx,ny)==false){
			node = findNode(nx, ny);
			moveParent(node, currentNode, 16);
		}
	}
	//Diagonal Left Up
		ny-=16;
	if(solid(nx,ny)==true){
		
		if(checkQueue(nx,ny)==true&&checkVector(nx,ny)==true){
			//cout<<" ("<<nx<<","<<ny<<") ";
		node = new tileNode(nx,ny);
		node->parent=currentNode;
		findG(22,node);
		findH(node);
		findF(node);
		opened.push_back(node);
		}
		else if(checkVector(nx,ny)==false){
			node = findNode(nx, ny);
			moveParent(node, currentNode, 22);
		}
	}
}

bool npc::solid(int nx, int ny){
	if(!mapOne->tileVector[mapOne->tile2DVector[nx/16][ny/16]]->solid&&!mapOne->tileVector[mapOne->tile2DVector[(nx+(width/2))/16][ny/16]]->solid&&!mapOne->tileVector[mapOne->tile2DVector[nx/16][(ny+(height/2))/16]]->solid&&!mapOne->tileVector[mapOne->tile2DVector[(nx+(width/2))/16][(y+(height/2))/16]]->solid){
		//cout<<" Solid ";
		return true;
	}
	else return false;
}

void npc::clickAdjustment(){
	if(mapOne->tileVector[mapOne->tile2DVector[goal_x/16][(goal_y+16)/16]]->solid)
		goal_y-=16;
	if(mapOne->tileVector[mapOne->tile2DVector[(goal_x+16)/16][goal_y/16]]->solid)
		goal_x-=16;
}


void npc::moveParent(tileNode* node, tileNode* current, int num){
	if(current->G+num<node->G){
		node->G=current->G+num;
		node->parent=current;
		findF(node);
	}
}

tileNode* npc::findNode(int nx, int ny){
		int size=opened.size();
	for(int i = 0; i < size; i++){
		if(nx == opened[i]->x&&ny==opened[i]->y)
			return opened[i];
	}
}

tileNode* npc::putIntoQueue(tileNode* current){
	tileNode* node = NULL;
	if(!opened.empty()){
	node=opened[0];
	int num=0; 
	for(int i = 0; i<opened.size(); i++){
		if(opened[i]->F < node->F){
			node=opened[i];
			num=i;
		}
	}	
	    closed.push_back(node);	
		opened.erase(opened.begin()+num);
		return node;
	}	
}

void npc::findG(int num, tileNode* node){
    node->G=node->parent->G+num;
}

void npc::findH(tileNode* node){
	if(node->x<=goal_x)
		node->H=goal_x-node->x;
	else node->H=node->x-goal_x;
	if(node->y<=goal_y)
		node->H=node->H+(goal_y-node->y);
	else node->H=node->H+(node->y-goal_y);
}

void npc::findF(tileNode* node){
    node->F=node->G+node->H;
}

bool npc::checkQueue(int nx, int ny){
	for(int i = 0; i < closed.size(); i++){
		if(closed[i]->x==nx&&closed[i]->y==ny)
		return false;
	}
	return true;
}

bool npc::checkVector(int nx, int ny){
	int size=opened.size();
	for(int i = 0; i < size; i++){
		if(opened[i]->x==nx&&opened[i]->y==ny)
		return false;
	}
	return true;
}

void npc::putIntoTemptVector(int nx, int ny, vector<tileNode*> temp, int num){
	int size = opened.size();
	for(int i = 0; i < size; i++){
		if(opened[i]->x==nx&&opened[i]->y==ny){
			temp.push_back(opened[i]);
			temp.back()->parent=closed.back();
			findG(num,temp.back());
			findF(temp.back());
			break;
		}
	}
}

void npc::backtrack()
{
	//cout<<"Goal: ("<<goal_x<<","<<goal_y<<") ";
	tileNode* node;
	node = closed.back();
	pathStack.push(node);
	while(node->parent!=NULL){
		node=node->parent;
		pathStack.push(node);
	//	cout<<" ("<<node->x<<","<<node->y<<") ";
	}
	//cout<<"||";
}

void npc::clearStack(){
	while(!pathStack.empty()){
		pathStack.pop();
	}
}
#endif
